package org.budo.graph.druid.spring.aop.interceptor;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.TargetSource;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import com.alibaba.druid.filter.stat.StatFilterContext;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import com.alibaba.druid.support.spring.stat.DruidStatInterceptor;
import com.alibaba.druid.support.spring.stat.SpringMethodInfo;
import com.alibaba.druid.support.spring.stat.SpringMethodStat;
import com.alibaba.druid.support.spring.stat.SpringStat;
import com.alibaba.druid.support.spring.stat.SpringStatManager;

/**
 * @author lmw
 * @see com.alibaba.druid.support.spring.stat.DruidStatInterceptor
 */
public class BudoGraphDruidSpringAopInterceptor implements MethodInterceptor, InitializingBean, DisposableBean, Serializable {
    private static final long serialVersionUID = -9060673283199987072L;

    public static final ThreadLocal<List<MethodInvocation>> METHOD_INVOCATION_THREAD_LOCAL = new InheritableThreadLocal<List<MethodInvocation>>();

    private static final transient Log log = LogFactory.getLog(DruidStatInterceptor.class);

    private static transient SpringStat SPRING_STAT = new SpringStat();

    private transient BudoGraphSpringMethodContextListener statContextListener = new BudoGraphSpringMethodContextListener();

    private static final Map<String, SpringMethodInfo> _SPRING_METHOD_INFO_CACHE_MAP = new ConcurrentHashMap<String, SpringMethodInfo>();

    private SpringMethodInfo springMethodInfo(Class<?> instanceType, Method method) {
        String _key = "" + instanceType + method;

        SpringMethodInfo _springMethodInfo = _SPRING_METHOD_INFO_CACHE_MAP.get(_key);
        if (null != _springMethodInfo) {
            return _springMethodInfo;
        }

        synchronized (BudoGraphDruidSpringAopInterceptor.class) {
            _springMethodInfo = _SPRING_METHOD_INFO_CACHE_MAP.get(_key);
            if (null != _springMethodInfo) {
                return _springMethodInfo;
            }

            _springMethodInfo = new BudoGraphSpringMethodInfo(instanceType, method);
            _SPRING_METHOD_INFO_CACHE_MAP.put(_key, _springMethodInfo);
            return _springMethodInfo; // 替换成 BudoGraphSpringMethodInfo 类
        }
    }

    public void afterPropertiesSet() throws Exception {
        SpringStatManager.getInstance().addSpringStat(SPRING_STAT);

        StatFilterContext.getInstance().addContextListener(statContextListener);
    }

    public void destroy() throws Exception {
        StatFilterContext.getInstance().removeContextListener(statContextListener);
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (null == METHOD_INVOCATION_THREAD_LOCAL.get()) {
            METHOD_INVOCATION_THREAD_LOCAL.set(new ArrayList<MethodInvocation>());
        }

        METHOD_INVOCATION_THREAD_LOCAL.get().add(invocation);

        SpringMethodStat lastMethodStat = SpringMethodStat.current();
        SpringMethodInfo methodInfo = this.getMethodInfo(invocation);
        SpringMethodStat methodStat = SPRING_STAT.getMethodStat(methodInfo, true);

        if (methodStat != null) {
            methodStat.beforeInvoke();
        }

        long startNanos = System.nanoTime();

        Throwable error = null;
        try {
            return invocation.proceed();
        } catch (Throwable e) {
            error = e;
            throw e;
        } finally {
            long endNanos = System.nanoTime();

            long nanos = endNanos - startNanos;

            if (methodStat != null) {
                methodStat.afterInvoke(error, nanos);
            }

            SpringMethodStat.setCurrent(lastMethodStat);

            METHOD_INVOCATION_THREAD_LOCAL.remove();
        }
    }

    public SpringMethodInfo getMethodInfo(MethodInvocation invocation) {
        Object _this = invocation.getThis();
        Method method = invocation.getMethod();

        if (null == _this) {
            return this.springMethodInfo(method.getDeclaringClass(), method);
        }

        if (method.getDeclaringClass() == _this.getClass()) {
            return this.springMethodInfo(method.getDeclaringClass(), method);
        }

        {
            Class<?> clazz = _this.getClass();
            boolean isCglibProxy = false;
            boolean isJavassistProxy = false;
            for (Class<?> item : clazz.getInterfaces()) {
                if (item.getName().equals("net.sf.cglib.proxy.Factory")) {
                    isCglibProxy = true;
                    break;
                } else if (item.getName().equals("javassist.util.proxy.ProxyObject")) {
                    isJavassistProxy = true;
                    break;
                }
            }

            if (isCglibProxy || isJavassistProxy) {
                Class<?> superClazz = clazz.getSuperclass();

                return this.springMethodInfo(superClazz, method);
            }
        }

        Class<?> clazz = null;
        try {
            for (int i = 0; i < 10; ++i) { // 最多支持10层代理
                if (_this instanceof org.springframework.aop.framework.Advised) {
                    TargetSource targetSource = ((org.springframework.aop.framework.Advised) _this).getTargetSource();

                    if (targetSource == null) {
                        break;
                    }

                    Object target = targetSource.getTarget();
                    if (target != null) {
                        _this = target;
                    } else {
                        clazz = targetSource.getTargetClass();
                        break;
                    }
                } else {
                    break;
                }
            }
        } catch (Throwable ex) {
            log.error("#135 getMethodInfo error", ex);
        }

        if (clazz == null) {
            return this.springMethodInfo(method.getDeclaringClass(), method);
        }

        return this.springMethodInfo(clazz, method);
    }
}